package com.topvision.opencv_test;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.ImageFormat;
import android.graphics.YuvImage;
import android.hardware.Camera;
import android.util.Log;

import org.opencv.android.Utils;
import org.opencv.core.Core;
import org.opencv.core.CvException;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.core.MatOfPoint;
import org.opencv.core.MatOfPoint2f;
import org.opencv.core.Point;
import org.opencv.core.Rect;
import org.opencv.core.RotatedRect;
import org.opencv.core.Size;
import org.opencv.imgproc.Imgproc;

import java.io.ByteArrayOutputStream;
import java.util.ArrayList;
import java.util.List;

/**
 * Author: lucy
 * Comment: //TODO
 * Date: 2017-10-11 16:55
 */

public class FindQRCodeUtils {
    public static final String TAG = "FindQRCodeUtils";

    public static ResultBean findQRCode(Camera camera,byte[] data) {
        Mat srcImage = getSourceMat(camera, data);
        Mat grayImage = new Mat(),
                gradientXImage = new Mat(),
                gradientYImage = new Mat(),
                gradientImage = new Mat(),
                blurImage = new Mat(),
                thresholdImage = new Mat(),
                morphImage = new Mat();

        //读取刚拍的图像
//        srcImage = Highgui.imread(strPicPath);
        //图像转换为灰度图像
        if (srcImage.channels() == 3) {
            Imgproc.cvtColor(srcImage, grayImage, Imgproc.COLOR_RGB2GRAY);
        } else {
//            grayImage = srcImage.clone();
            Imgproc.cvtColor(srcImage, grayImage, Imgproc.COLOR_RGBA2GRAY);
        }
        //建立图像的梯度幅值
        Imgproc.Scharr(grayImage, gradientXImage, CvType.CV_32F, 1, 0);
        Imgproc.Scharr(grayImage, gradientYImage, CvType.CV_32F, 0, 1);

        //因为我们需要的条形码在需要X方向水平,所以更多的关注X方向的梯度幅值,而省略掉Y方向的梯度幅值
        Core.subtract(gradientXImage, gradientYImage, gradientImage);

        //归一化为八位图像
        Core.convertScaleAbs(gradientImage, gradientImage);

        //对图片进行相应的模糊化,使一些噪点消除
        Imgproc.blur(gradientImage, blurImage, new Size(9, 9));

        //模糊化以后进行阈值化,得到到对应的黑白二值化图像,二值化的阈值可以根据实际情况调整
        Imgproc.threshold(blurImage, thresholdImage, 210, 255, Imgproc.THRESH_BINARY);

        //二值化以后的图像,条形码之间的黑白没有连接起来,就要进行形态学运算,消除缝隙,相当于小型的黑洞,选择闭运算
        //因为是长条之间的缝隙,所以需要选择宽度大于长度
        Mat kernel = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(21, 7));
        Imgproc.morphologyEx(thresholdImage, morphImage, Imgproc.MORPH_CLOSE, kernel);

        //现在要让条形码区域连接在一起,所以选择膨胀腐蚀,而且为了保持图形大小基本不变,应该使用相同次数的膨胀腐蚀
        //先腐蚀,让其他区域的亮的地方变少最好是消除,然后膨胀回来,消除干扰,迭代次数根据实际情况选择
        Imgproc.erode(morphImage, morphImage, Imgproc.getStructuringElement(Imgproc.MORPH_ELLIPSE, new Size(3, 3)), new Point(-1, -1), 4);
        Imgproc.dilate(morphImage, morphImage, Imgproc.getStructuringElement(Imgproc.MORPH_ELLIPSE, new Size(3, 3)), new Point(-1, -1), 4);

        //接下来对目标轮廓进行查找,目标是为了计算图像面积
        List<MatOfPoint> contours = new ArrayList<>();

        Imgproc.findContours(morphImage, contours, new Mat(),
                Imgproc.RETR_LIST, Imgproc.CHAIN_APPROX_NONE);

        //找出面积最大的轮廓
        double maxValue = 0;
        int maxIndex = 0;
        for (int i = 0; i < contours.size(); ++i) {
            if (Imgproc.contourArea(contours.get(i)) > maxValue) {
                maxValue = Imgproc.contourArea(contours.get(i));
                maxIndex = i;
            }
        }
        if (contours.size() <= 0) {
            return null;
        }
        //根据轮廓生成旋转矩形
        MatOfPoint2f NewMtx = new MatOfPoint2f(contours.get(maxIndex).toArray());
        RotatedRect minRect = Imgproc.minAreaRect(NewMtx);
        Point[] vertices = new Point[4];
        minRect.points(vertices);

        if (minRect.angle < 2.0) {
            //找到了矩形的角度,但是这是一个旋转矩形,所以还要重新获得一个外包最小矩形
            Rect rect = Imgproc.boundingRect(contours.get(maxIndex));
            //把这个矩形在源图像中画出来
//            Core.rectangle(srcImage,myRect,Scalar(0,255,255),3,LINE_AA);
            //将扫描的图像裁剪下来,并保存为相应的结果,保留一些X方向的边界,所以对rect进行一定的扩张
            rect.x = rect.x - (rect.width / 20);
            rect.width = (int) (rect.width * 1.1f);
            try {
                Mat resultImage = new Mat(srcImage, rect);
                return new ResultBean(rect,srcImage,resultImage);
            } catch (CvException exception) {
                camera.autoFocus(new Camera.AutoFocusCallback() {
                    @Override
                    public void onAutoFocus(boolean success, Camera camera) {

                    }
                });
            }
            //保存到本地
//            Highgui.imwrite(Environment.getExternalStorageDirectory() + "/1Zxing/"
//                    + System.currentTimeMillis() + "contour.jpg", resultImage);
        }
        return null;
    }

    private static Mat getSourceMat(Camera camera,byte[] data) {
        byte[] rawData = getRawData(camera, data);
        Mat srcImage = new Mat();
        Bitmap bitmap = null;
        try {
            bitmap = BitmapFactory.decodeByteArray(rawData, 0, rawData.length);
            bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(),
                    bitmap.getHeight());
            Utils.bitmapToMat(bitmap, srcImage);
        } catch (OutOfMemoryError e) {
            //
            Log.e(TAG, "findQRCode: OOM异常");
        }
        bitmap.recycle();
        return srcImage;
    }

    private static byte[] getRawData(Camera camera,byte[] data) {
        Camera.Size previewSize = camera.getParameters().getPreviewSize();
        BitmapFactory.Options newOpts = new BitmapFactory.Options();
        newOpts.inJustDecodeBounds = true;
        YuvImage yuvimage = new YuvImage(
                data,
                ImageFormat.NV21,
                previewSize.width,
                previewSize.height,
                null);
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        // JPG图片的质量[0-100],100最高
        yuvimage.compressToJpeg(new android.graphics.Rect(0, 0, previewSize.width, previewSize.height), 100, baos);
        return baos.toByteArray();
    }
}
